using System;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace UDS.Management.Data.Package
{
    /// <summary>
    /// Loads, validates, and serializes the manifest object from disk or from the specified assembly
    /// </summary>
    internal class ManifestHelper : IDisposable
    {
		private XmlTextReader manifestXsdReader = null;
		private XmlTextReader guidXsdReader = null;
		private XmlReaderSettings readerSettings = null;

		private ManifestHelper()
		{
			Stream XsdStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(SCHEMA_MANIFEST_NAME);
			manifestXsdReader = new XmlTextReader(XsdStream);

			Stream GuidXsdStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(SCHEMA_GUID_NAME);
			guidXsdReader = new XmlTextReader(GuidXsdStream);

			readerSettings = new XmlReaderSettings();
			readerSettings.ValidationType = ValidationType.Schema;
			readerSettings.Schemas.ValidationEventHandler += new ValidationEventHandler(ReaderSettings_ValidationEventHandler);
			readerSettings.ValidationEventHandler += new ValidationEventHandler(ReaderSettings_ValidationEventHandler);
			readerSettings.Schemas.Add("http://microsoft.com/wsdl/types/", guidXsdReader);
            readerSettings.Schemas.Add("http://www.sosceo.com/server/DBManifest", manifestXsdReader);
		}
		

        /// <summary>
        /// The name of the database manifest xsd file in the assembly
        /// </summary>
        private const string SCHEMA_MANIFEST_NAME = "UDS.Management.Data.DBManifest.xsd";
		/// <summary>
		/// The name of the guid schema file in the assembly
		/// </summary>
		private const string SCHEMA_GUID_NAME = "UDS.Management.Data.GuidSchema.xsd";

        public string ManifestPath = null;

        public ManifestHelper(string manifestPath) : this()
        {
            ManifestPath = manifestPath;
        }

		/// <summary>
		/// Load the database manifest from the assembly loader
		/// </summary>
		/// <exception cref="Exceptions.PackageValidationException">A PackageValidationException is thrown if the manifest does not match the schema</exception>
		/// <param name="manifestStream">The manifest stream to validate</param>
		/// <returns>A database manifest object</returns>
		internal DatabaseManifest Load(System.IO.Stream manifestStream)
		{			
			// Now serialize the manifest
			using (XmlReader ValidatingReader = XmlTextReader.Create(manifestStream, readerSettings))
			{
				try
				{
					// Deserialize the stream
					XmlSerializer Serializer = new XmlSerializer(typeof(DatabaseManifest));

					return (DatabaseManifest)Serializer.Deserialize(ValidatingReader);
				}
				catch (InvalidOperationException ioEx)
				{
					// Treat the package validation exception specifically so callers
					// can differentiate between this and an unhandled exception
					if (ioEx.InnerException is Exceptions.PackageValidationException)
					{
						Exceptions.PackageValidationException PackageEx = ioEx.InnerException as Exceptions.PackageValidationException;
						PackageEx.PackageName = ManifestPath;
						throw PackageEx;
					}
					else
					{
						throw;
					}
				}
			}
		}

        private void ReaderSettings_ValidationEventHandler(object sender, ValidationEventArgs e)
        {
			if (e.Severity == XmlSeverityType.Error)
			{
				string Message = e.Message.TrimEnd('.');
				Exceptions.PackageValidationException ex = new Exceptions.PackageValidationException(String.Format("{0} at line {1}, position {2}.", Message, e.Exception.LineNumber, e.Exception.LinePosition));
				throw ex;
			}
        }

		#region IDisposable Members

		public void Dispose()
		{
			if (guidXsdReader != null) guidXsdReader.Close();
			if (manifestXsdReader != null) manifestXsdReader.Close();
		}

		#endregion
	}
}
